package com.wulis.config.swagger;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.wulis.common.annotation.ApiVersion;
import com.wulis.common.model.enums.VersionEnum;
import com.wulis.common.properties.SwaggerProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.SpringfoxWebMvcConfiguration;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.List;

/**
 * @author WuliBao
 * @date 2020-03-26
 */
@Slf4j
@Component
@EnableKnife4j
@EnableSwagger2
@ConditionalOnClass(SpringfoxWebMvcConfiguration.class)
@EnableConfigurationProperties(SwaggerProperties.class)
public class SwaggerConfig implements WebMvcConfigurer {
    
    private final SwaggerProperties swaggerProperties;
    
    private final ConfigurableApplicationContext applicationContext;
    
    /**
     * 初始化 applicationContext 初始化 swaggerProperties 动态创建 Docket
     *
     * @param applicationContext applicationContext
     */
    public SwaggerConfig(ConfigurableApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        this.swaggerProperties = applicationContext.getBean(SwaggerProperties.class);
        // 手动注册RestApi
        for (VersionEnum version : VersionEnum.values()) {
            // 注册bean
            Docket docket = registerDocketBean(version.version());
            // 构建Docket
            buildRestApi(docket, version.version());
        }
    }
    
    /**
     * 构建Docket
     *
     * @param docket docket
     * @param version version
     */
    public void buildRestApi(Docket docket, String version) {
        docket.groupName(version)
            .apiInfo(apiInfo(version))
            // 设置安全模式，swagger可以设置访问token
            .securitySchemes(securitySchemes())
            .securityContexts(securityContexts())
            .select()
            .apis(input -> {
                assert input != null;
                boolean present = input.findAnnotation(ApiVersion.class).isPresent();
                if (present) {
                    ApiVersion apiVersion = input.findAnnotation(ApiVersion.class).get();
                    // 判断当前方法是否为该版本
                    return version.equals(apiVersion.value().version());
                }
                return false;
            })
            .paths(PathSelectors.any())
            .build();
    }
    
    /**
     * 安全模式，这里指定token通过Authorization头请求头传递
     *
     * @return List<ApiKey>
     */
    private List<ApiKey> securitySchemes() {
        List<ApiKey> apiKeyList = new ArrayList<>();
        apiKeyList.add(new ApiKey("Authorization", "Authorization", "header"));
        return apiKeyList;
    }
    
    /**
     * 安全上下文
     *
     * @return List<SecurityContext>
     */
    private List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(SecurityContext.builder()
            .securityReferences(defaultAuth())
            .forPaths(PathSelectors.regex("^(?!auth).*$"))
            .build());
        return securityContexts;
    }
    
    /**
     * 默认的安全上引用
     *
     * @return List<SecurityReference>
     */
    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
        return securityReferences;
    }
    
    /**
     * API信息描述（取自yaml中信息）
     *
     * @return ApiInfo
     */
    private ApiInfo apiInfo(String versionNum) {
        return new ApiInfoBuilder().title(swaggerProperties.getTitle())
            .description(swaggerProperties.getDescription())
            .contact(new Contact(swaggerProperties.getContactName(), swaggerProperties.getContactUrl(),
                swaggerProperties.getContactEmail()))
            .version(versionNum)
            .build();
    }
    
    /**
     * 注册DocketBean
     *
     * @return Docket
     */
    private Docket registerDocketBean(String name) {
        if (applicationContext.containsBean(name)) {
            Object bean = applicationContext.getBean(name);
            if (bean.getClass().isAssignableFrom(Docket.class)) {
                return (Docket)bean;
            }
            else {
                throw new RuntimeException("BeanName 重复 " + name);
            }
        }
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Docket.class);
        // 为bean进行初始化构建
        beanDefinitionBuilder.addConstructorArgValue(DocumentationType.SWAGGER_2);
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry)applicationContext.getBeanFactory();
        // 注入到容器中
        beanFactory.registerBeanDefinition(name, beanDefinition);
        return applicationContext.getBean(name, Docket.class);
    }
    
    /**
     * 添加静态资源
     *
     * @param registry registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}